如同 Function Component 一樣,Hook 也要是單純的 JavaScript Function
然後再加上二個必要的原則
所以只會在二種情境下呼叫 Hooks
因為 React Hook 是用「順序」來記憶及對應在邏輯區塊呼叫 React Hook 的地方
如果在 Condition / Loop / Nested Function 中呼叫 React Hook,則容易發生問題
在元件內的 Top Level 呼叫 Hook 會是最好的作法
可以觀察接下來的範例解說,來理解為什麼要遵守這個原則
首先,我們可以在單一的元件中使用多個 State 或 Effect Hook
function Form() {
// 1. 使用 name state 變數
const [name, setName] = useState('Mary');
// 2. 使用一個 effect 來保存表單
useEffect(function persistForm() {
localStorage.setItem('formData', name);
});
// 3. 使用 surname state 變數
const [surname, setSurname] = useState('Poppins');
// 4. 使用一個 effect 來更新標題
useEffect(function updateTitle() {
document.title = name + ' ' + surname;
});
// ...
}
可以發現,在每一次的 Render 中 Hook 都是依照一樣的順序被呼叫
// ------------
// 第一次 render
// ------------
useState('Mary') // 1. 用 'Mary' 來初始化 name state 變數
useEffect(persistForm) // 2. 增加一個 effect 來保存表單
useState('Poppins') // 3. 用 'Poppins' 來初始化 surname state 變數
useEffect(updateTitle) // 4. 增加一個 effect 來更新標題
// -------------
// 第二次 render
// -------------
useState('Mary') // 1. 讀取 name state 變數
useEffect(persistForm) // 2. 替換了用來保存表單的 effect
useState('Poppins') // 3. 讀取 surname state 變數
useEffect(updateTitle) // 4. 替換了用來更新標題的 effect
// ...
但如果我們把一個 Hook 呼叫,放在條件式中會發生什麼事呢?
function Form() {
// 1. 使用 name state 變數
const [name, setName] = useState('Mary');
// 2. 使用一個 effect 來保存表單
// ? 違反原則 - 在條件式中使用 Hook
if (name !== '') {
useEffect(function persistForm() {
localStorage.setItem('formData', name);
});
}
// 3. 使用 surname state 變數
const [surname, setSurname] = useState('Poppins');
// 4. 使用一個 effect 來更新標題
useEffect(function updateTitle() {
document.title = name + ' ' + surname;
});
// ...
}
初次 Render 有呼叫對應的 Hook
但是後續 Render 時被跳過了,接下來的 Hook 順序也對應不上了
// ------------
// 第一次 render
// ------------
useState('Mary') // 1. 用 'Mary' 來初始化 name state 變數
useEffect(persistForm) // 2. 增加一個 effect 來保存表單
useState('Poppins') // 3. 用 'Poppins' 來初始化 surname state 變數
useEffect(updateTitle) // 4. 增加一個 effect 來更新標題
// -------------
// 第二次 render
// -------------
useState('Mary') // 1. 讀取 name state 變數
// useEffect(persistForm) // X 這個 Hook 被跳過了!
useState('Poppins') // X 2 (但之前是 3). 未能讀取 surname state 變數
useEffect(updateTitle) // X 3 (但之前是 4). 未能取代 effect
// ...
在我們跳過的那個 Hook 後面,每下一個 Hook 呼叫,都會 shift 一個,導致 bug 的發生
function Form() {
// 1. 使用 name state 變數
const [name, setName] = useState('Mary');
// 2. 使用一個 effect 來保存表單
useEffect(function persistForm() {
// ? 調整判斷,把判斷放在 Hooks 內,以符合原則
if (name !== '') {
localStorage.setItem('formData', name);
}
});
// 3. 使用 surname state 變數
const [surname, setSurname] = useState('Poppins');
// 4. 使用一個 effect 來更新標題
useEffect(function updateTitle() {
document.title = name + ' ' + surname;
});
// ...
}
如此一來,每次 Render 就會按照順序執行,不會有被跳過的情境發生
因此可以發現,只在元件的 Top Level 呼叫 Hook,把有分支邏輯的做法寫在 Hook 內,讓每個 Hook 都能按照順序被執行
可以搭配使用 ESLINT 工具 eslint-plugin-react-hooks
,提示我們不要破規原則
這個 plugin 已經預設在 CRA 的設定。
// Your ESLint configuration
{
"plugins": [
// ...
"react-hooks"
],
"rules": {
// ...
"react-hooks/rules-of-hooks": "error", // Checks rules of Hooks
"react-hooks/exhaustive-deps": "warn" // Checks effect dependencies
}
}
所有的 Hook ,無論是官方提供還是客製化的,都要以 use
開頭來命名。
use
開頭,才能讓 React 檢查它是否違反前面提到的 Hook 原則。
所以有哪些 Hook 可以幫助 React 元件,使用 React 各項功能的特殊 Function,
接下來就更進一步理解每個 Hook 的用法及使用時機吧!
https://pjchender.dev/react/react-doc-hooks-into/
https://zh-hant.reactjs.org/docs/hooks-rules.html